/////////////////////////////////////////////////////////////////////////////////
// Paint.NET                                                                   //
// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors.     //
// Portions Copyright (C) Microsoft Corporation. All Rights Reserved.          //
// See src/Resources/Files/License.txt for full licensing and attribution      //
// details.                                                                    //
// .                                                                           //
/////////////////////////////////////////////////////////////////////////////////

using System;
using System.Drawing;

namespace PaintDotNet
{
    /// <summary>
    /// Adapted from: 
    /// "A Primer on Building a Color Picker User Control with GDI+ in Visual Basic .NET or C#"
    /// http://www.msdnaa.net/Resources/display.aspx?ResID=2460
    /// </summary>
    public struct HsvColor
    {
        public int Hue; // 0-360
        public int Saturation; // 0-100
        public int Value; // 0-100

        public static bool operator== (HsvColor lhs, HsvColor rhs)
        {
            if ((lhs.Hue == rhs.Hue) &&
                (lhs.Saturation == rhs.Saturation) &&
                (lhs.Value == rhs.Value))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        public static bool operator!= (HsvColor lhs, HsvColor rhs)
        {
            return !(lhs == rhs);
        }

        public override bool Equals(object obj)
        {
            return this == (HsvColor)obj;
        }

        public override int GetHashCode()
        {
            return (Hue + (Saturation << 8) + (Value << 16)).GetHashCode();;
        }

        public HsvColor(int hue, int saturation, int value) 
        {
            if (hue < 0 || hue > 360)
            {
                throw new ArgumentOutOfRangeException("hue", "must be in the range [0, 360]");
            }

            if (saturation < 0 || saturation > 100)
            {
                throw new ArgumentOutOfRangeException("saturation", "must be in the range [0, 100]");
            }

            if (value < 0 || value > 100)
            {
                throw new ArgumentOutOfRangeException("value", "must be in the range [0, 100]");
            }

            Hue = hue;
            Saturation = saturation;
            Value = value;
        }

        public static HsvColor FromColor(Color color)
        {
            RgbColor rgb = new RgbColor(color.R, color.G, color.B);
            return rgb.ToHsv();
        }

        public Color ToColor()
        {
            RgbColor rgb = ToRgb();
            return Color.FromArgb(rgb.Red, rgb.Green, rgb.Blue);
        }

        public RgbColor ToRgb() 
        {
            // HsvColor contains values scaled as in the color wheel:

            double h;
            double s;
            double v;

            double r = 0;
            double g = 0;
            double b = 0;

            // Scale Hue to be between 0 and 360. Saturation
            // and value scale to be between 0 and 1.
            h = (double) Hue % 360;
            s = (double) Saturation / 100;
            v = (double) Value / 100;

            if (s == 0)
            {
                // If s is 0, all colors are the same.
                // This is some flavor of gray.
                r = v;
                g = v;
                b = v;
            } 
            else 
            {
                double p;
                double q;
                double t;

                double fractionalSector;
                int sectorNumber;
                double sectorPos;

                // The color wheel consists of 6 sectors.
                // Figure out which sector you're in.
                sectorPos = h / 60;
                sectorNumber = (int)(Math.Floor(sectorPos));

                // get the fractional part of the sector.
                // That is, how many degrees into the sector
                // are you?
                fractionalSector = sectorPos - sectorNumber;

                // Calculate values for the three axes
                // of the color. 
                p = v * (1 - s);
                q = v * (1 - (s * fractionalSector));
                t = v * (1 - (s * (1 - fractionalSector)));

                // Assign the fractional colors to r, g, and b
                // based on the sector the angle is in.
                switch (sectorNumber) 
                {
                    case 0:
                        r = v;
                        g = t;
                        b = p;
                        break;

                    case 1:
                        r = q;
                        g = v;
                        b = p;
                        break;

                    case 2:
                        r = p;
                        g = v;
                        b = t;
                        break;

                    case 3:
                        r = p;
                        g = q;
                        b = v;
                        break;

                    case 4:
                        r = t;
                        g = p;
                        b = v;
                        break;

                    case 5:
                        r = v;
                        g = p;
                        b = q;
                        break;
                }
            }
            // return an RgbColor structure, with values scaled
            // to be between 0 and 255.
            return new RgbColor((int)(r * 255), (int)(g * 255), (int)(b * 255));
        }

        public override string ToString() 
        {
            return String.Format("({0}, {1}, {2})", Hue, Saturation, Value);
        }
    }
}
